package com.example.palyerdemo

import android.content.Context
import android.widget.SeekBar
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.ClippingConfiguration
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.SeekParameters
import androidx.media3.ui.PlayerView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

/**
 * 封装带区间跳过与逻辑时长映射的播放器。
 *
 * 能力与职责：
 * - 管理 [ExoPlayer] 的创建、绑定与释放，统一生命周期。
 * - 维护 [SegmentManager]，支持“勾选=删除”的段落控制，不改动真实媒体源，只通过映射/跳转实现“逻辑删除”。
 * - 提供 逻辑时间(去除已删除区间后的时间线) 与 真实时间 的双向映射；
 * - 绑定 [SeekBar]：拖拽基于逻辑时间，松手映射至真实时间后精准 seek。
 * - 精准 seek：默认使用 [SeekParameters.EXACT]，命中毫秒级位置（非关键帧对齐）。
 * - UI 进度回调：以主线程定时器(协程)回调逻辑位置与逻辑总时长，便于宿主更新 UI 覆层。
 *
 * 注意事项：
 * - 必须调用 [release] 以取消内部协程并释放播放器，避免内存泄漏。
 * - [totalDurationMs] 与 [segmentCount] 用于初始化平均分段；如需自定义分段，可扩展 API 替换默认初始化。
 * - 仅做“数据层删除”，真实媒体完整播放可被映射跳过，不会修改媒体文件。
 */
@OptIn(UnstableApi::class)
class SegmentedPlayer(
    context: Context,
    private val playerView: PlayerView,
    private val totalDurationMs: Long,
    segmentCount: Int,
    private val onProgress: (logicalPositionMs: Long, logicalDurationMs: Long) -> Unit
) {

    private val player: ExoPlayer = ExoPlayer.Builder(context).build()
    private val segmentManager: SegmentManager = SegmentManager(totalDurationMs, segmentCount)

    private var isUserSeeking: Boolean = false
    private var seekBar: SeekBar? = null
    private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
    private var tickerJob: Job? = null
    private var boundOwner: LifecycleOwner? = null
    private var lifecycleObserver: DefaultLifecycleObserver? = null
    private var released: Boolean = false

    init {
        player.setSeekParameters(SeekParameters.EXACT)
        playerView.player = player
        startTicker()
    }

    /**
     * 设置播放地址并开始播放。
     *
     * 注意：
     * - 会裁剪到 [totalDurationMs] 结束，便于演示/测试逻辑时间。
     * - 若传入流式资源，请确保已授予网络权限并在外部处理代理/证书。
     */
    fun setUri(uri: String) {
        val mediaItem = MediaItem.Builder()
            .setUri(uri)
            .setClippingConfiguration(
                ClippingConfiguration.Builder()
                    .setEndPositionMs(totalDurationMs)
                    .build()
            )
            .build()
        player.setMediaItem(mediaItem)
        player.prepare()
        player.playWhenReady = true
    }

    /**
     * 绑定宿主生命周期：
     * - onStart 自动播放（可配置），onStop 自动暂停；
     * - onDestroy 默认自动 [release]（可配置），并解除观察者；
     * - 重复绑定会先解绑旧的 owner。
     */
    fun attachToLifecycle(owner: LifecycleOwner, playWhenStarted: Boolean = true, releaseOnDestroy: Boolean = true) {
        detachFromLifecycle()
        val observer = object : DefaultLifecycleObserver {
            override fun onStart(owner: LifecycleOwner) {
                if (playWhenStarted && !released) play()
            }

            override fun onStop(owner: LifecycleOwner) {
                if (!released) pause()
            }

            override fun onDestroy(owner: LifecycleOwner) {
                if (releaseOnDestroy && !released) release()
                owner.lifecycle.removeObserver(this)
            }
        }
        owner.lifecycle.addObserver(observer)
        boundOwner = owner
        lifecycleObserver = observer
    }

    /** 解除生命周期绑定。*/
    fun detachFromLifecycle() {
        val owner = boundOwner
        val observer = lifecycleObserver
        if (owner != null && observer != null) {
            owner.lifecycle.removeObserver(observer)
        }
        boundOwner = null
        lifecycleObserver = null
    }

    /**
     * 绑定宿主页面的 [SeekBar]。
     * - 用户拖拽时修改“逻辑时间”，松手后自动换算到真实时间并精准 [seekTo]。
     * - 组件内部会定期刷新进度条的最大值与进度，开发者无需手动维护。
     */
    fun attachSeekBar(seekBar: SeekBar) {
        this.seekBar = seekBar
        seekBar.max = segmentManager.remainingDurationMs().toInt()
        seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {}
            override fun onStartTrackingTouch(seekBar: SeekBar?) { isUserSeeking = true }
            override fun onStopTrackingTouch(seekBar: SeekBar?) {
                val targetLogical = (seekBar?.progress ?: 0).toLong()
                seekToLogical(targetLogical)
                isUserSeeking = false
            }
        })
    }

    /**
     * 将逻辑时间位置转换为真实时间后进行精准 seek。
     * - 超过逻辑总时长会被夹在最后一段末尾。
     */
    fun seekToLogical(logicalPositionMs: Long) {
        val targetReal = segmentManager.logicalToReal(logicalPositionMs)
        player.seekTo(targetReal)
    }

    /**
     * 标记/取消标记某段为“已删除(需要跳过)”。
     * - 会立即刷新进度上限，且若当前处于已删除区间，会跳到下一可播放区间。
     */
    fun setDeleted(index: Int, deleted: Boolean) {
        segmentManager.setDeleted(index, deleted)
        onSegmentsChanged()
    }

    /** 当前剩余的逻辑总时长（去除被删除区间后的总时长，毫秒）。 */
    fun remainingLogicalDurationMs(): Long = segmentManager.remainingDurationMs()

    /** 获取段落的不可变快照，避免外部直接篡改内部状态。 */
    fun snapshotSegments(): List<Segment> = segmentManager.segments.map { it.copy() }

    /** 开始播放。*/
    fun play() { player.play() }
    /** 暂停播放。*/
    fun pause() { player.pause() }

    /**
     * 释放资源。必须在宿主生命周期结束时调用，防止协程与播放器泄漏。
     */
    fun release() {
        if (released) return
        released = true
        tickerJob?.cancel()
        detachFromLifecycle()
        // 断开与 View 的绑定，避免持有导致泄漏
        playerView.player = null
        player.release()
    }

    /** 段落变更后的内部处理：刷新进度上限，并在必要时跳出已删除区间。 */
    private fun onSegmentsChanged() {
        seekBar?.max = segmentManager.remainingDurationMs().toInt()
        val current = player.currentPosition
        maybeSkipIfInDeleted(current)
    }

    /**
     * 启动 UI 进度协程：
     * - 250ms 周期回调进度与总时长；
     * - 主线程执行，确保与 UI 同步；
     * - 调用 [release] 或重复调用本方法都会安全取消旧任务。
     */
    private fun startTicker() {
        tickerJob?.cancel()
        tickerJob = scope.launch {
            while (isActive) {
                val realPosition = player.currentPosition
                maybeSkipIfInDeleted(realPosition)
                val logicalPosition = segmentManager.realToLogical(player.currentPosition)
                val logicalDuration = segmentManager.remainingDurationMs()
                seekBar?.let { bar ->
                    bar.max = logicalDuration.toInt()
                    if (!isUserSeeking) {
                        bar.progress = logicalPosition.toInt().coerceIn(0, bar.max)
                    }
                }
                onProgress(logicalPosition, logicalDuration)
                delay(250)
            }
        }
    }

    private fun maybeSkipIfInDeleted(realPositionMs: Long) {
        val currentSegment = segmentManager.findSegmentByPosition(realPositionMs)
        if (currentSegment != null && currentSegment.deleted) {
            val nextStart = segmentManager.nextKeptStartAfter(realPositionMs)
            if (nextStart != null) {
                player.seekTo(nextStart)
            } else {
                player.pause()
            }
        }
    }
}


